#include <JXQCore/JXQGlobal>
#if defined(J_OS_UNIX) || defined(J_OS_LINUX)

#include "jprocess_p.h"
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>

namespace JXQ
{

int JProcessPrivate::pipe()
{
	for(int i=0; i<3; i++)
	{
		if( ::pipe2(m_pipeFd[i], O_NONBLOCK) < 0 )
		{
			int res = errno;
			for(; i>=0; i--)
			{
				::close(m_pipeFd[i][0]);
				::close(m_pipeFd[i][1]);
			}
			return res;
		}
	}

	fcntl(m_pipeFd[0][0], F_SETFL, fcntl(m_pipeFd[0][0], F_GETFL) & ~O_NONBLOCK);
	return 0;
}

void JProcessPrivate::createWatcher(JHandle pid)
{
	if( m_watcher != nullptr )
		delete m_watcher;

	m_watcher = new JProcessWatcher(pid);
	registerDescriptor(m_pipeFd[1][0]);
	registerDescriptor(m_pipeFd[2][0]);

	m_watcher.operator JProcessWatcher*()->finished.subscribe(
				[this](int result) mutable
	{
		J_Q(JProcess)
		q->close();

		unregisterDescriptor(m_pipeFd[1][0]);
		unregisterDescriptor(m_pipeFd[2][0]);

		for(int i=0; i<3; i++)
		{
			::close(m_pipeFd[i][0]);
			::close(m_pipeFd[i][1]);
		}

		q->finished.send(result);
	});
}

void JProcessPrivate::event(int fd, JIONotifier::Type event)
{
	if( not (event & JIONotifier::Read) )
		return ;

	int i = fd==m_pipeFd[1][0]? 1:2;
	int64_t size = m_buffer[i].size();
	char *buf = new char[size];

	for(;;)
	{
		memset(buf, 0, size);
		int realSize = ::read(fd, buf, size);

		if( realSize < 0 )
		{
			int err = errno;
			if( err != EAGAIN and err != EWOULDBLOCK )
				jError() << "JProcess: read event: syscall 'read' failed:" << strerror(err);
			break;
		}
		else if( realSize == 0 )
			break;

		if( m_bufMode == JAbstractIO::FullBuffer )
		{
			m_buffer[i].write(buf, realSize);
			q_ptr->readyRead.send(q_ptr);
		}
		else
		{
			int64_t sum = 0;
			for(int64_t j=0; j<realSize; j++)
			{
				if( buf[j] == '\n' )
				{
					int64_t size = j + 1 - sum;
					m_buffer[i].write(buf + sum, size);
					sum += size;
					q_ptr->readyRead.send(q_ptr);
				}
			}
			if( sum < realSize )
			{
				m_buffer[i].write(buf + sum, realSize - sum);
				if( m_buffer[i].isFull() )
					q_ptr->readyRead.send(q_ptr);
			}
		}
	}

	delete[] buf;
}

/*----------------------------------------------------------------------------------*/

JHandle JProcess::start
(const JString &fileName, JStringList args)
{
	J_D(JProcess)

	if( d->m_watcher != nullptr )
		return (d->m_watcher).operator JProcessWatcher*()->pid();

	if( fileName.empty() )
	{
		jError() << "JProcess::start: file name is empty.";
		setErrno(-EINVAL);
		return -1;
	}

	static const JBasicString<char> flag[4] = {"/", "//", "\\", "\\\\"};
	size_t index = 0;
	int flagSize = 0;

	for(const auto &str : flag)
	{
		index = fileName.rfind(str);
		if( index != JString::npos )
		{
			flagSize = str.size();
			break;
		}
	}

	if( index == JString::npos )
		args.insert(0, fileName);
	else if( index + flagSize < fileName.size() )
		args.insert(0, fileName.substr(index+flagSize));
	else
	{
		jError() << "JProcess::start:" << fileName << "is a directory";
		setErrno(-EINVAL);
	}
	auto __argv = args.c_strV2();

	int res = d->pipe();
	if( res < 0 )
	{
		setErrno(res);
		return res;
	}

	pid_t pid = fork();
	if( pid < 0 )
	{
		setErrno(errno);
		return pid;
	}

	else if( pid == 0 ) //child
	{
		dup2(d->m_pipeFd[0][0], STDIN_FILENO);
		dup2(d->m_pipeFd[1][1], STDOUT_FILENO);
		dup2(d->m_pipeFd[2][1], STDERR_FILENO);

		res = execvp(fileName, J_CCT(char**, __argv.get()));

		perror("execvp");
		exit(res);
	}

	d->createWatcher(pid);
	open(ReadWrite);
	return pid;
}

JHandle JProcess::start
(ProcessMain &&processMain, const JStringList &args)
{
	J_D(JProcess)

	if( d->m_watcher != nullptr )
		return (d->m_watcher).operator JProcessWatcher*()->pid();

	int res = d->pipe();
	if( res < 0 )
	{
		setErrno(res);
		return res;
	}

	pid_t pid = fork();
	if( pid < 0 )
	{
		setErrno(errno);
		return pid;
	}

	else if( pid == 0 ) //child
	{
		dup2(d->m_pipeFd[0][0], STDIN_FILENO);
		dup2(d->m_pipeFd[1][1], STDOUT_FILENO);
		dup2(d->m_pipeFd[2][1], STDERR_FILENO);

		exit(processMain(args));
	}

	d->createWatcher(pid);
	open(ReadWrite);
	return pid;
}

JHandle JProcess::detach()
{
	J_D(JProcess)
	if( d->m_watcher != nullptr )
	{
		d->unregisterDescriptor(d->m_pipeFd[1][0]);
		d->unregisterDescriptor(d->m_pipeFd[2][0]);

		JHandle pid = d->m_watcher.operator JProcessWatcher*()->pid();
		delete d->m_watcher;
		d->m_watcher = nullptr;

		close();
		for(int i=0; i<3; i++)
		{
			::close(d->m_pipeFd[i][0]);
			::close(d->m_pipeFd[i][1]);
		}
		return pid;
	}
	return 0;
}

bool JProcess::kill(bool force)
{
	J_D(JProcess)
	if( d->m_watcher == nullptr )
		return false;

	JHandle pid = d->m_watcher.operator JProcessWatcher*()->pid();
	if( pid > 0 )
	{
		if( ::kill(pid, force? SIGKILL : SIGTERM) < 0 )
		{
			setErrno(errno);
			return false;
		}
	}
	return true;
}

bool JProcess::kill(JHandle handle, bool force)
{
	if( handle > 0 )
		return ::kill(handle, force? SIGKILL : SIGTERM) == 0;
	return false;
}

void JProcess::setBufferMode(BufferMode mode)
{
	setErrno(0);
	J_D(JProcess)

	JAbstractIO::setBufferMode(mode);
	if( mode == FullBuffer )
		return ;

	// 行缓冲
	int64_t index = d->m_buffer[0].rfind('\n');
	if( index >= 0 )
	{
		char *buf = new char[d->m_buffer[0].size()];
		d->m_buffer[0].read(buf, ++index);

		d->pipeWriteLock.lock();
		::write(d->m_pipeFd[0][1], buf, index);
		d->pipeWriteLock.unlock();

		delete buf;
	}

	index = d->m_buffer[1].find('\n');
	index = d->m_buffer[2].find('\n');
	if( index >= -1 )
		readyRead.send(this);
}

void JProcess::setBufferSize(int64_t size)
{
	J_D(JProcess)

	int64_t available = d->m_buffer[0].bytesAvailable();
	if( available < size )
	{
		char *buf = new char[d->m_buffer[0].size()];
		d->m_buffer[0].read(buf, available);

		d->pipeWriteLock.lock();
		::write(d->m_pipeFd[0][1], buf, available);
		d->pipeWriteLock.unlock();

		delete buf;
	}
	d->m_buffer[0].setBufferSize(size);

	available = d->m_buffer[1].bytesAvailable();
	int64_t available2 = d->m_buffer[2].bytesAvailable();

	if( available < size or available2 < size )
		readyRead.send(this);

	d->m_buffer[0].setBufferSize(size);
	d->m_buffer[1].setBufferSize(size);
}

void JProcess::flush()
{
	setErrno(0);
	J_D(JProcess)

	if( not d_ptr->m_isOpen )
		return ;

	int64_t size = d->m_buffer[0].size();
	char *buf = new char[size];
	d->m_buffer[0].read(buf, size);

	d->pipeWriteLock.lock();
	int res = ::write(d->m_pipeFd[0][1], buf, size);
	d->pipeWriteLock.unlock();

	if( res < 0 )
		jError() << "JProcess: flush: write failed:" << strerror(errno);

	delete buf;
}

int64_t JProcess::virtualWrite(int64_t bufSize, const void *buffer)
{
	setErrno(0);

	J_D(JProcess)
	if( d->check(WriteOnly) == false )
		return -1;

	auto lambda_write = [d, buffer](int64_t addSize) -> int
	{
		int64_t size = d->m_buffer[0].bytesAvailable();
		char *_buf = new char[size + addSize];

		d->m_buffer[0].read(_buf, size);
		memcpy(_buf + size, buffer, addSize);

		d->pipeWriteLock.lock();
		int res = ::write(d->m_pipeFd[0][1], _buf, size + addSize);
		d->pipeWriteLock.unlock();

		if( res < 0 )
			jError() << "JProcess: flush: write failed:" << strerror(errno);

		delete _buf;
		return res;
	};

	// 缓冲区满 刷新
	if( bufSize > d->m_buffer[0].bytesWritable() )
		return lambda_write(bufSize);

	// 行缓冲模式 检测'\n' 刷新
	if( d->m_bufMode == LineBuffer )
	{
		const char *charBuffer = J_RCT(const char*, buffer);
		int64_t i = bufSize - 1;
		int64_t sum = 0;

		for(; i>=0; i--)
		{
			if( charBuffer[i] == '\n' )
			{
				sum = lambda_write(i + 1);
				if( sum < 0 )
					sum = 0;
			}
		}
		return d->m_buffer[0].write(charBuffer + sum, bufSize - sum) + sum;
	}

	// 写入缓冲区
	return d->m_buffer[0].write(buffer, bufSize);
}

} //namespace JXQ

#endif
